EDA#

Librerias#

import pandas as pd
import os
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import missingno as msno
import matplotlib.pyplot as plt
import seaborn as sns

import sweetviz as sv

Cargar datos y Limpieza de dataset#

Cargar datos#

  • Ruta de la carpeta que contiene los archivos CSV

carpeta = ‘C:/UNINORTE/VC/Proyecto2/dataset_ventas’

  • Obtener la lista de archivos CSV en la carpeta

archivos_csv = [archivo for archivo in os.listdir(carpeta) if archivo.endswith(‘.csv’)]

  • Crear un DataFrame vacío para almacenar los datos combinados

ventas = pd.DataFrame()

  • Leer cada archivo CSV y combinarlo en el DataFrame datos_combinados

for archivo in archivos_csv: ruta_archivo = os.path.join(carpeta, archivo) datos_archivo = pd.read_csv(ruta_archivo, index_col=0) ventas = pd.concat([ventas, datos_archivo], ignore_index=True)

  • Descargar los datos en un excel

ventas.to_excel(‘datos.xlsx’, index=False)

Despues de hacer merch entre todos los documentos csv, se hace una descarga del archivo final y de aquí en adelante usaremos este archivo.

ventas = pd.read_excel('C:/UNINORTE/VC/Proyecto2/datos.xlsx')
ventas.head(10)
lat long id date category location mode price details description surface rooms baths park
0 NaN -0.000105 6416237 2021-06-20 Apartamento Bogotá Galerias Venta $148.000.000 ['Área Const.:\r 44,00 ... ApartamentoInteriorPrimerPisoremodeladoSalaCom... _x000D_44,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_1_x000D__x000D_ _x000D__x000D_Sinespecificar_x000D__x000D_
1 NaN -0.000105 6416237 2021-06-20 Apartamento Bogotá Galerias Venta $148.000.000 ['Área Const.:\r 44,00 ... ApartamentoInteriorPrimerPisoremodeladoSalaCom... _x000D_44,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_1_x000D__x000D_ _x000D__x000D_Sinespecificar_x000D__x000D_
2 NaN -0.000105 6416237 2021-06-20 Apartamento Bogotá Galerias Venta $148.000.000 ['Área Const.:\r 44,00 ... ApartamentoInteriorPrimerPisoremodeladoSalaCom... _x000D_44,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_1_x000D__x000D_ _x000D__x000D_Sinespecificar_x000D__x000D_
3 NaN -0.000105 6416237 2021-06-20 Apartamento Bogotá Galerias Venta $148.000.000 ['Área Const.:\r 44,00 ... ApartamentoInteriorPrimerPisoremodeladoSalaCom... _x000D_44,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_1_x000D__x000D_ _x000D__x000D_Sinespecificar_x000D__x000D_
4 NaN NaN 6461572 2021-07-25 Apartamento Pereira El nogal Venta $215.000.000 ['Área Const.:\r 65,00 ... FantásticoApartamentoubicadoenelclubresidencia... _x000D_65,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_2_x000D__x000D_ _x000D__x000D_Parqueaderos:1_x000D__x000D_
5 NaN NaN 6461572 2021-07-25 Apartamento Pereira El nogal Venta $215.000.000 ['Área Const.:\r 65,00 ... FantásticoApartamentoubicadoenelclubresidencia... _x000D_65,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_2_x000D__x000D_ _x000D__x000D_Parqueaderos:1_x000D__x000D_
6 NaN NaN 6461572 2021-07-25 Apartamento Pereira El nogal Venta $215.000.000 ['Área Const.:\r 65,00 ... FantásticoApartamentoubicadoenelclubresidencia... _x000D_65,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_2_x000D__x000D_ _x000D__x000D_Parqueaderos:1_x000D__x000D_
7 NaN NaN 6461572 2021-07-25 Apartamento Pereira El nogal Venta $215.000.000 ['Área Const.:\r 65,00 ... FantásticoApartamentoubicadoenelclubresidencia... _x000D_65,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_2_x000D__x000D_ _x000D__x000D_Parqueaderos:1_x000D__x000D_
8 NaN NaN 6461572 2021-07-25 Apartamento Pereira El nogal Venta $215.000.000 ['Área Const.:\r 65,00 ... FantásticoApartamentoubicadoenelclubresidencia... _x000D_65,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_2_x000D__x000D_ _x000D__x000D_Parqueaderos:1_x000D__x000D_
9 NaN NaN 6461572 2021-07-25 Apartamento Pereira El nogal Venta $215.000.000 ['Área Const.:\r 65,00 ... FantásticoApartamentoubicadoenelclubresidencia... _x000D_65,00m²_x000D__x000D_ _x000D__x000D_Habitaciones:_x000D_3_x000D__x000D_ _x000D__x000D_Baños:_x000D_2_x000D__x000D_ _x000D__x000D_Parqueaderos:1_x000D__x000D_

Limpiza de datos#

# Reemplazar "Aconsultar" por NaN en la columna price
ventas['price'] = ventas['price'].replace('Aconsultar', np.nan)
# Eliminar el signo $, los puntos y los espacios en blanco de la columna price
ventas['price'] = ventas['price'].str.replace('[\$,\. ]', '', regex=True)

# Convertir la columna price a tipo numérico
ventas['price'] = pd.to_numeric(ventas['price'])
# Convertir la columna date a formato de fecha
ventas['date'] = pd.to_datetime(ventas['date'])
# Establecer valores fuera del rango válido como NaN en la columna 'lat', es decir, todos lo valores que esten fuera de las latitudes mínima y máxima de Colombia
ventas.loc[(ventas['lat'] < -4.227) | (ventas['lat'] > 12.450), 'lat'] = np.nan

# Establecer valores fuera del rango válido como NaN en la columna 'long', es decir, todos lo valores que esten fuera de las longitudes mínima y máxima de Colombia
ventas.loc[(ventas['long'] < -79.000) | (ventas['long'] > -67.000), 'long'] = np.nan
# Dividir la columna 'location' en dos nuevas columnas: 'ciudad' y 'barrio'
ventas[['ciudad', 'barrio']] = ventas['location'].str.split(' ', n=1, expand=True)

# Eliminar la columna 'location'
ventas.drop(columns=['location'], inplace=True)
# Eliminar "_x000D_" de las columnas especificadas
cols_to_clean = ['surface', 'rooms', 'baths', 'park']
for col in cols_to_clean:
    ventas[col] = ventas[col].str.replace('_x000D_', '', regex=False)
# Eliminar "m²", puntos y comas de la columna 'surface', convertir a formato numérico y dividir por 100 para que se tenga en cuenta los decimales
ventas['surface'] = ventas['surface'].str.replace('m²', '', regex=False).str.replace('.', '', regex=False).str.replace(',', '', regex=False).astype(float) / 100
# Reemplazar "Sinespecificar" por NaN y eliminar "Habitaciones:" de la columna 'rooms'
ventas['rooms'] = ventas['rooms'].replace('Sinespecificar', np.nan).str.replace('Habitaciones:', '', regex=False).astype(float)

# Reemplazar "Sinespecificar" por NaN y eliminar "Baños:" de la columna 'baths'
ventas['baths'] = ventas['baths'].replace('Sinespecificar', np.nan).str.replace('Baños:', '', regex=False).astype(float)
# Limpiar y transformar la columna 'park'
ventas['park'] = ventas['park'].replace('Sinespecificar', '0')\
                               .str.replace('Parqueaderos:', '', regex=False)\
                               .replace('Másde10', '11')\
                               .astype(float)
# Eliminar filas donde 'mode' es "Arriendo"
ventas = ventas[ventas['mode'] != 'Arriendo']

# Eliminar la columna 'mode'
ventas.drop(columns=['mode'], inplace=True)
# Palabras a eliminar
words_to_remove = ['VENTA', 'VENDO', 'APTO', 'BARRIO']

# Eliminar palabras específicas y repetición del nombre de la ciudad
ventas['barrio'] = ventas.apply(lambda row: ' '.join(word for word in (row['barrio'] or '').split() if word.upper() not in words_to_remove and word.upper() != row['ciudad'].upper()), axis=1)

A continuación, trataremos por separado la columna details, dado que tiene muchos datos útiles para el análisis, pero están juntos dentro de una misma columna y de forma que no se pueden interpretar correctamente

# Crear un nuevo DataFrame solo con la columna 'details'
nuevo_df = pd.DataFrame(ventas['details'])

# Dividir la columna 'details' en varias columnas separando el texto por '''
nuevo_df = nuevo_df['details'].str.split("'", expand=True)

# Renombrar las columnas del nuevo DataFrame
nuevo_df.columns = [f'detalle_{i}' for i in range(nuevo_df.shape[1])]
# Lista de columnas a eliminar
columnas_a_eliminar = ['detalle_0', 'detalle_2', 'detalle_4', 'detalle_6', 'detalle_8', 'detalle_10', 'detalle_12', 'detalle_14', 'detalle_16', 'detalle_18', 'detalle_20']

# Eliminar las columnas especificadas
nuevo_df.drop(columns=columnas_a_eliminar, inplace=True)
# Nombres de las columnas del nuevo DataFrame
nombres_columnas = ["Área privada", "Área Const.", "Precio m²", "Admón", "Estrato", "Estado", "Antigüedad", "Piso No", "Tipo de Apartamento", "Sector"]

# Crear el nuevo DataFrame con las columnas especificadas
df_final = pd.DataFrame(columns=nombres_columnas)

# Función para extraer el valor asociado a un nombre de columna en una fila del DataFrame original
def extraer_valor(fila, nombre_columna):
    for elemento in fila:
        if isinstance(elemento, str) and nombre_columna.lower() in elemento.lower():
            return elemento.split(':')[1].strip()
    return None

# Llenar el nuevo DataFrame buscando los valores correspondientes en cada fila de nuevo_df
for nombre_columna in nombres_columnas:
    df_final[nombre_columna] = nuevo_df.apply(lambda fila: extraer_valor(fila, nombre_columna), axis=1)
df_final.head()
Área privada Área Const. Precio m² Admón Estrato Estado Antigüedad Piso No Tipo de Apartamento Sector
0 None \r 44,00 m² \r 3.363.636/m² None \r 4\r \r None \r 9 a 15 años None None Ver Mapa
1 None \r 44,00 m² \r 3.363.636/m² None \r 4\r \r None \r 9 a 15 años None None Ver Mapa
2 None \r 44,00 m² \r 3.363.636/m² None \r 4\r \r None \r 9 a 15 años None None Ver Mapa
3 None \r 44,00 m² \r 3.363.636/m² None \r 4\r \r None \r 9 a 15 años None None Ver Mapa
4 None \r 65,00 m² \r 3.307.692/m² None \r 4\r \r None None None None Ver Mapa

A continuación, concatenar esta nueva información con nuestro dataframe ‘ventas’

# Concatenar los DataFrames a lo largo del eje de las columnas
ventas = pd.concat([ventas, df_final], axis=1)

Eliminaremos las siguientes columnas:

  • ‘details’: La información contenida en esta columna ya está divida en otras columnas (pasos realizados anteriormente).

  • ‘description’: Es un valor único para cada vivienda y es subjetivo.

  • ‘date’: Es la fecha en la que se cargó la información de la vivienda a la pagina, lo cual no influye en el costo de la vivienda.

  • ‘id’: Es el código asignado a la vivienda al momento del registro y es un valor único, no influye en el costo de la vivienda.

  • ‘Precio m²’: Es una columna calculada a partir de la columna ‘price’ y ‘Área const.’.

# Eliminar la columna 'details'
ventas.drop(columns=['details'], inplace=True)

# Eliminar la columna 'description'
ventas.drop(columns=['description'], inplace=True)

# Eliminar la columna 'date'
ventas.drop(columns=['date'], inplace=True)

# Eliminar la columna 'Precio m²'
ventas.drop(columns=['Precio m²'], inplace=True)
# Limpieza y conversión de las columnas "Área privada" y "Área Const." en el DataFrame ventas
for columna in ["Área privada", "Área Const."]:
    ventas[columna] = (ventas[columna]
                       .str.replace('m²', '', regex=False)
                       .str.replace('.', '', regex=False)
                       .str.replace(',', '', regex=False)
                       .str.replace('\r', '', regex=False)
                       .str.replace('\\r', '', regex=False)
                       .str.strip()
                       .astype(float) / 100)
# Limpieza de la columna "Admón"
ventas['Admón'] = (ventas['Admón']
                   .str.replace('$', '', regex=False)
                   .str.replace(',', '', regex=False)
                   .str.replace('\r', '', regex=False)
                   .str.replace('\\r', '', regex=False)
                   .str.replace('Incluida', '0', regex=False)
                   .str.strip()
                   .astype(float))
# Limpieza de la columna "Estrato"
ventas['Estrato'] = (ventas['Estrato']
                     .str.strip()
                     .str.replace('\r', '', regex=False)
                     .str.replace('\\r', '', regex=False)
                     .replace('Campestre', np.nan)
                     .replace('                    Campestre                    ', np.nan)
                     .astype(float))
# Limpieza de la columna "Estado"
ventas['Estado'] = (ventas['Estado']
                    .str.replace('\r', '', regex=False)
                    .str.replace('\\r', '', regex=False)
                    .str.strip())
# Mostrar los valores únicos en la columna "Estado"
valores_unicos_estado = ventas['Estado'].unique()
print(valores_unicos_estado)
[None 'Bueno' 'Excelente' 'Remodelar']

A continuación, cambiaremos los valores de la columna estado para pasarlos a números, teniendo en cuenta que esto es una calificación de la vivienda.

# Cambiar los valores en la columna "Estado"
ventas['Estado'] = ventas['Estado'].replace({'Remodelar': 3, 'Bueno': 4, 'Excelente': 5})
C:\Users\Linda Herrera\AppData\Local\Temp\ipykernel_17900\1796510237.py:2: FutureWarning: Downcasting behavior in `replace` is deprecated and will be removed in a future version. To retain the old behavior, explicitly call `result.infer_objects(copy=False)`. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`
  ventas['Estado'] = ventas['Estado'].replace({'Remodelar': 3, 'Bueno': 4, 'Excelente': 5})
# Cambiar el nombre de la columna "Antigüedad" a "Antiguedad"
ventas.rename(columns={'Antigüedad': 'Antiguedad'}, inplace=True)
# Limpieza de la columna "Antigüedad"
ventas['Antiguedad'] = (ventas['Antiguedad']
                        .str.replace('\\r', '', regex=False)    
                        .str.strip())
# Limpieza de la columna "Piso No"
ventas['Piso No'] = (ventas['Piso No']
                     .str.replace('\r', '', regex=False)
                     .str.replace('\\r', '', regex=False)   
                     .str.replace('º', '', regex=False)
                     .str.replace('ª', '', regex=False)
                     .str.strip()
                     .replace('Otros', np.nan)
                     .astype(float))
# Limpieza de la columna "Tipo de Apartamento"
ventas['Tipo de Apartamento'] = (ventas['Tipo de Apartamento']
                                 .str.replace('\r', '', regex=False)
                                 .str.replace('\\r', '', regex=False)  
                                 .str.strip())
# Cambiar valores en 'Tipo de Apartamento' a 'No Aplica' cuando 'category' es 'Casa'
ventas.loc[ventas['category'] == 'Casa', 'Tipo de Apartamento'] = 'No Aplica'
# Limpieza de la columna "Sector"
ventas['Sector'] = (ventas['Sector']
                    .str.replace('Ver Mapa', '', regex=False)
                    .replace('', np.nan)  # Reemplazar cadenas vacías resultantes con NaN
                    .str.strip())
# Restablecer el índice del DataFrame
ventas = ventas.reset_index(drop=True)

Eliminar columnas duplicadas y eliminar la columna:

  • Los duplicados se eliminarán de acuerdo con los id de vivienda duplicados y teniendo en cuenta que las filas que queden sean las más completas.

  • ‘id’: Es el código asignado a la vivienda al momento del registro y es un valor único, no influye en el costo de la vivienda.

# Eliminar filas duplicadas manteniendo la fila con más datos completos - Para aseguridad de que un mismo inmueble no tuviera 2 id diferentes
ventas = (ventas.sort_values(by=ventas.columns.tolist(), na_position='last')
                .drop_duplicates(subset=['id'], keep='first')
                .drop_duplicates())
# Eliminar la columna 'id'
ventas.drop(columns=['id'], inplace=True)
ventas.head(10)
lat long category price surface rooms baths park ciudad barrio Área privada Área Const. Admón Estrato Estado Antiguedad Piso No Tipo de Apartamento Sector
407717 -3.556624 NaN Apartamento 197000000.0 64.0 2.0 2.0 1.0 Cali Ciudad Bochalema 64.0 64.0 178000.0 4.0 NaN 1 a 8 años NaN None Ciudad Bochalema
407715 -2.889093 NaN Casa 650000000.0 192.0 5.0 3.0 0.0 Bogotá Antiguo Copihue 192.0 192.0 NaN 3.0 NaN 1 a 8 años NaN No Aplica Zona Norte
407713 -2.577413 -73.038536 Apartaestudio 70000000.0 30.0 1.0 1.0 0.0 Bogotá Las Lomas 30.0 30.0 45000.0 2.0 NaN 16 a 30 años NaN None Las Lomas
407710 -2.193166 NaN Apartamento 235000000.0 56.0 3.0 2.0 1.0 Medellín Calasanz Occidente 56.0 56.0 192139.0 3.0 NaN None 13.0 None NaN
407708 -2.037440 NaN Apartaestudio 139000000.0 43.0 1.0 1.0 1.0 Barranquilla Ciudad Jardín 43.0 43.0 145000.0 4.0 NaN None 4.0 None NaN
407706 -1.054628 -73.300781 Apartamento 260000000.0 115.0 4.0 2.0 0.0 Medellín Centro NaN 115.0 NaN 4.0 4.0 9 a 15 años 3.0 None Centro
407703 -0.341669 -78.530228 Apartamento 175000000.0 63.0 3.0 2.0 1.0 Cartagena Ciudad Jardin 63.0 63.0 115000.0 3.0 NaN None 7.0 None NaN
407700 -0.181263 NaN Apartamento 153000000.0 74.0 3.0 2.0 1.0 Barranquilla Miramar 74.0 74.0 190000.0 4.0 NaN None 5.0 None NaN
407699 -0.175781 -77.255859 Casa 390000000.0 160.0 6.0 3.0 1.0 Medellín Occidente 160.0 160.0 NaN 3.0 4.0 16 a 30 años 1.0 No Aplica Occidente
407696 -0.148553 NaN Apartamento 145000000.0 54.0 3.0 1.0 0.0 Bogotá Ciudad Tintal 54.0 54.0 NaN 3.0 NaN 9 a 15 años NaN None Ciudad Tintal
ventas.info()
<class 'pandas.core.frame.DataFrame'>
Index: 169528 entries, 407717 to 34421
Data columns (total 19 columns):
 #   Column               Non-Null Count   Dtype  
---  ------               --------------   -----  
 0   lat                  169191 non-null  float64
 1   long                 149730 non-null  float64
 2   category             169528 non-null  object 
 3   price                169515 non-null  float64
 4   surface              169526 non-null  float64
 5   rooms                167914 non-null  float64
 6   baths                168167 non-null  float64
 7   park                 169528 non-null  float64
 8   ciudad               169528 non-null  object 
 9   barrio               169528 non-null  object 
 10  Área privada         120204 non-null  float64
 11  Área Const.          169526 non-null  float64
 12  Admón                105780 non-null  float64
 13  Estrato              167101 non-null  float64
 14  Estado               98550 non-null   float64
 15  Antiguedad           139813 non-null  object 
 16  Piso No              101698 non-null  float64
 17  Tipo de Apartamento  57552 non-null   object 
 18  Sector               134150 non-null  object 
dtypes: float64(13), object(6)
memory usage: 25.9+ MB

Análisis Descriptivo#

ventas.describe()
lat long price surface rooms baths park Área privada Área Const. Admón Estrato Estado Piso No
count 169191.000000 149730.000000 1.695150e+05 1.695260e+05 167914.000000 168167.000000 169528.000000 1.202040e+05 1.695260e+05 1.057800e+05 167101.000000 98550.000000 101698.00000
mean 5.037213 -74.856828 1.923391e+10 2.467323e+03 3.278226 2.719404 1.293946 1.948304e+03 2.467323e+03 1.824785e+06 4.354959 4.492532 4.19373
std 2.789628 0.884258 5.534035e+12 3.904603e+05 2.059622 1.523848 1.237870 5.486953e+05 3.904603e+05 3.205592e+07 1.280686 0.543905 3.11103
min -3.556624 -78.530228 1.530000e+02 1.000000e+00 1.000000 1.000000 0.000000 1.000000e+00 1.000000e+00 -1.794967e+09 1.000000 3.000000 1.00000
25% 4.594397 -75.567032 2.500000e+08 6.800000e+01 3.000000 2.000000 1.000000 6.700000e+01 6.800000e+01 1.750000e+05 3.000000 4.000000 2.00000
50% 4.704000 -74.197063 3.950000e+08 1.000000e+02 3.000000 2.000000 1.000000 1.000000e+02 1.000000e+02 3.150000e+05 4.000000 5.000000 3.00000
75% 6.189920 -74.066002 6.800000e+08 1.720000e+02 4.000000 3.000000 2.000000 1.720000e+02 1.720000e+02 5.600000e+05 6.000000 5.000000 5.00000
max 11.400610 -67.130997 1.989188e+15 1.000000e+08 254.000000 253.000000 11.000000 1.900000e+08 1.000000e+08 1.900000e+09 6.000000 5.000000 16.00000

Análisis de datos atípicos#

Price#

# Crear un histograma del precio
fig = px.histogram(ventas, x='price', title='Histograma de precio',
                   labels={'price': 'Precio'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()

Evidenemente existen muchos datos atipicos

# Crear un histograma del precio para cada estrato
fig = px.histogram(ventas, x='price', color='Estrato', barmode='overlay',
                   title='Histograma de precio por estrato', 
                   labels={'price': 'Precio'},
                   opacity=0.75)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()
# Crear un histograma del precio para cada ciudad
fig = px.histogram(ventas, x='price', color='ciudad', barmode='overlay',
                   title='Histograma de precio por ciudad', 
                   labels={'price': 'Precio'},
                   opacity=0.75)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()
# Calcular el primer y decimo cuartil
Q1 = ventas['price'].quantile(0.10)
Q3 = ventas['price'].quantile(0.90)

# Calcular el rango intercuartílico (IQR)
IQR = Q3 - Q1

# Definir los límites para los valores atípicos usando un rango más amplio
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# Identificar los valores atípicos
outliers = ventas[(ventas['price'] < lower_bound) | (ventas['price'] > upper_bound)]['price']

# Mostrar los valores atípicos y su cantidad
print("Valores atípicos en 'price':")
print(outliers)
print("\nCantidad de valores atípicos:", len(outliers))

# Imprimir porcentaje de valores atípicos
porc = (len(outliers) / len(ventas)) * 100
print("\nPorcentaje de valores atípicos:", porc)
Valores atípicos en 'price':
29757     3.300000e+09
29746     3.480000e+09
28208     4.500000e+09
28225     4.000000e+09
17364     2.900000e+09
              ...     
408071    3.708000e+09
408062    5.269941e+09
407972    4.900000e+09
407993    5.582565e+09
161       1.200000e+12
Name: price, Length: 3086, dtype: float64

Cantidad de valores atípicos: 3086

Porcentaje de valores atípicos: 1.820348261054221
# Crear un DataFrame con solo los valores atípicos
outliers_df = ventas[(ventas['price'] < lower_bound) | (ventas['price'] > upper_bound)]
# Contar la cantidad de datos atípicos por ciudad
cantidad_atipicos_por_ciudad = outliers_df['ciudad'].value_counts().reset_index()
cantidad_atipicos_por_ciudad.columns = ['ciudad', 'cantidad']

# Gráfico de barras de la cantidad de datos atípicos por ciudad
fig = px.bar(cantidad_atipicos_por_ciudad, x='ciudad', y='cantidad', color='ciudad',
             title='Cantidad de datos atípicos por ciudad')
fig.show()
# Contar la cantidad de datos atípicos por Estrato
cantidad_atipicos_por_e = outliers_df['Estrato'].value_counts().reset_index()
cantidad_atipicos_por_e.columns = ['Estrato', 'cantidad']

# Gráfico de barras de la cantidad de datos atípicos por Estrato
fig = px.bar(cantidad_atipicos_por_e, x='Estrato', y='cantidad', color='Estrato',
             title='Cantidad de datos atípicos por Estrato')

# Calcular el promedio del precio por estrato
promedio_precio_por_estrato = outliers_df.groupby('Estrato')['price'].mean().reset_index()
promedio_precio_por_estrato.columns = ['Estrato', 'Promedio']

# Crear una figura con dos ejes y
fig = make_subplots(specs=[[{"secondary_y": True}]])

# Agregar gráfico de barras para la cantidad de datos atípicos por Estrato
fig.add_trace(
    go.Bar(x=cantidad_atipicos_por_e['Estrato'], y=cantidad_atipicos_por_e['cantidad'], name='Cantidad de datos atípicos', marker_color='rgb(55, 83, 109)'),
    secondary_y=False,
)

# Agregar línea del promedio del precio por Estrato
fig.add_trace(
    go.Scatter(x=promedio_precio_por_estrato['Estrato'], y=promedio_precio_por_estrato['Promedio'], name='Promedio del precio', line=dict(color='red'), mode='lines+markers'),
    secondary_y=True,
)

# Agregar título y etiquetas de los ejes
fig.update_layout(
    title_text="Cantidad de datos atípicos y Promedio del precio por Estrato",
    xaxis_title="Estrato",
)

# Establecer nombres de los ejes y
fig.update_yaxes(title_text="Cantidad de datos atípicos", secondary_y=False)
fig.update_yaxes(title_text="Promedio del precio", secondary_y=True)

# Mostrar el gráfico
fig.show()
outliers_df.head()
lat long category price surface rooms baths park ciudad barrio Área privada Área Const. Admón Estrato Estado Antiguedad Piso No Tipo de Apartamento Sector
29757 0.0 NaN Casa 3.300000e+09 540.0 4.0 4.0 4.0 Bogotá Guaymaral 540.0 540.0 1350000.0 6.0 5.0 Menos de 1 año 1.0 No Aplica Guaymaral
29746 0.0 NaN Casa 3.480000e+09 650.0 6.0 5.0 9.0 Bogotá Chapinero NaN 650.0 NaN 5.0 5.0 16 a 30 años 2.0 No Aplica Chapinero
28208 0.0 NaN Casa 4.500000e+09 350.0 3.0 4.0 1.0 Medellín LAS PALMAS NaN 350.0 NaN 4.0 NaN None NaN No Aplica NaN
28225 0.0 NaN Casa 4.000000e+09 740.0 4.0 5.0 4.0 Cali Santa Teresita NaN 740.0 NaN 6.0 5.0 9 a 15 años 2.0 No Aplica Santa Teresita
17364 0.0 NaN Apartamento 2.900000e+09 464.0 4.0 5.0 4.0 Bogotá Los Rosales 464.0 464.0 2300000.0 6.0 5.0 Más de 30 años 8.0 None Los Rosales

Eliminación:

# Eliminar los outliers de la columna 'price'
ventas = ventas[(ventas['price'] >= lower_bound) & (ventas['price'] <= upper_bound)]
# Crear un histograma del precio despues de eliminar los atipicos
fig = px.histogram(ventas, x='price', title='Histograma de precio',
                   labels={'price': 'Precio'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Precio', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()

Rooms#

# Crear un histograma de rooms
fig = px.histogram(ventas, x='rooms', title='Histograma de cantidad de habitaciones',
                   labels={'rooms': 'Habitaciones'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Habitaciones', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()
# Filtrar filas con valores de 'rooms' superiores a 50
rooms_mayor_50 = ventas[ventas['rooms'] > 50]

# Mostrar las filas resultantes
print(rooms_mayor_50)
             lat       long     category        price  surface  rooms  baths  \
383287  3.383238 -76.518471  Apartamento  140000000.0     61.0  254.0    2.0   
287599  4.672591 -74.053879  Apartamento  870000000.0    123.0  253.0  253.0   
251228  4.699450 -74.037437  Apartamento  880000000.0    185.0   66.0    3.0   
163647  5.046391 -75.520950         Casa  190000000.0     72.0   60.0    3.0   
147601  6.123374 -75.384232  Apartamento  450000000.0     80.0  116.0    2.0   
116116  6.238554 -75.602364         Casa  920000000.0    247.0  253.0    3.0   

        park     ciudad         barrio  Área privada  Área Const.     Admón  \
383287   0.0       Cali       El Caney           NaN         61.0     150.0   
287599   2.0     Bogotá      El Virrey         115.0        123.0  580000.0   
251228   2.0     Bogotá  Santa Bárbara         166.0        185.0  600000.0   
163647   0.0  Manizales     VILLAMARIA          72.0         72.0       NaN   
147601   1.0   Medellín     SurOriente          80.0         80.0     232.0   
116116   2.0   Medellín       Laureles         279.0        247.0       NaN   

        Estrato  Estado      Antiguedad  Piso No Tipo de Apartamento  \
383287      4.0     5.0  Más de 30 años      NaN                None   
287599      6.0     5.0     9 a 15 años      2.0                None   
251228      6.0     4.0    16 a 30 años      3.0                None   
163647      3.0     NaN    16 a 30 años      NaN           No Aplica   
147601      6.0     NaN      1 a 8 años      NaN                None   
116116      5.0     4.0  Más de 30 años      NaN           No Aplica   

               Sector  
383287       Zona Sur  
287599     Zona Norte  
251228  Santa Bárbara  
163647            NaN  
147601     SurOriente  
116116      Occidente  

Evidentemente es un error, por ende, imputaremos con el promedio de las habitaciones de los inmuebles que tengan igual la cuidad, category y Estrato de inmueble.

# Definir una función para imputar el promedio de 'rooms'
def imputar_promedio_rooms(row):
    if row['rooms'] > 50:
        # Filtrar filas con la misma ciudad, categoría y estrato
        filtro = ventas[(ventas['ciudad'] == row['ciudad']) &
                        (ventas['category'] == row['category']) &
                        (ventas['Estrato'] == row['Estrato'])]
        
        # Calcular el promedio de 'rooms' para el grupo
        promedio_rooms = filtro['rooms'].mean()
        
        # Imputar el valor promedio
        row['rooms'] = promedio_rooms
    
    return row

# Aplicar la función para imputar los valores de 'rooms'
ventas = ventas.apply(imputar_promedio_rooms, axis=1)
# Crear un histograma de rooms
fig = px.histogram(ventas, x='rooms', title='Histograma de cantidad de habitaciones',
                   labels={'rooms': 'Habitaciones'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Habitaciones', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()

Baths#

# Crear un histograma de baths
fig = px.histogram(ventas, x='baths', title='Histograma de cantidad de baños',
                   labels={'baths': 'Baños'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Baños', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()
# Filtrar filas con valores de 'baths' superiores a 50
baths_mayor_30 = ventas[ventas['baths'] > 30]

# Mostrar las filas resultantes
print(baths_mayor_30)
              lat       long     category         price  surface      rooms  \
33545    0.000000        NaN         Casa  1.300000e+09    500.0        NaN   
364862   3.461000 -76.528999         Casa  4.800000e+08    210.0   4.000000   
339292   4.610315 -74.196404         Casa  3.000000e+08    391.0  11.000000   
291837   4.669000 -74.041000  Apartamento  1.450000e+09    264.0   3.000000   
287599   4.672591 -74.053879  Apartamento  8.700000e+08    123.0   2.739168   
146184   6.178152 -75.572960  Apartamento  7.500000e+08    144.0   3.000000   
67162   10.975321 -74.784279         Casa  4.900000e+08    484.0  30.000000   
66742   10.979000 -74.800003         Casa  1.600000e+09    900.0  35.000000   

        baths  park        ciudad             barrio  Área privada  \
33545    77.0   1.0      Medellín         EL POBLADO           NaN   
364862   32.0  11.0          Cali          Versalles         210.0   
339292   44.0   1.0        Bogotá  Bosa Los Naranjos         140.0   
291837   52.0   2.0        Bogotá         Chico Alto         264.0   
287599  253.0   2.0        Bogotá          El Virrey         115.0   
146184   35.0   2.0      Medellín  LOMA BENEDICTINOS         144.0   
67162    32.0   0.0  Barranquilla       Chiquinquirá         247.0   
66742    33.0   0.0  Barranquilla             Lucero         900.0   

        Área Const.      Admón  Estrato  Estado      Antiguedad  Piso No  \
33545         500.0        NaN      6.0     NaN            None      1.0   
364862        210.0        NaN      5.0     4.0  Más de 30 años      2.0   
339292        391.0        NaN      2.0     4.0    16 a 30 años      NaN   
291837        264.0  1100000.0      6.0     4.0    16 a 30 años      4.0   
287599        123.0   580000.0      6.0     5.0     9 a 15 años      2.0   
146184        144.0   350000.0      5.0     NaN      1 a 8 años      NaN   
67162         484.0        NaN      3.0     4.0    16 a 30 años      NaN   
66742         900.0        NaN      3.0     4.0      1 a 8 años      NaN   

       Tipo de Apartamento             Sector  
33545            No Aplica                NaN  
364862           No Aplica         Zona Norte  
339292           No Aplica  Bosa Los Naranjos  
291837                None         Zona Norte  
287599                None         Zona Norte  
146184                None             Centro  
67162            No Aplica        Sur Oriente  
66742            No Aplica      Metropolitana  

Evidentemente es un error, por ende, imputaremos con el promedio de las habitaciones de los inmuebles que tengan igual la cuidad, category y Estrato de inmueble.

# Definir una función para imputar el promedio de 'baths'
def imputar_promedio_baths(row):
    if row['baths'] > 30: 
        # Filtrar filas con la misma ciudad, categoría y estrato
        filtro = ventas[(ventas['ciudad'] == row['ciudad']) &
                        (ventas['category'] == row['category']) &
                        (ventas['Estrato'] == row['Estrato'])]
        
        # Calcular el promedio de 'baths' para el grupo
        promedio_baths = filtro['baths'].mean()
        
        # Imputar el valor promedio
        row['baths'] = promedio_baths
    
    return row

# Aplicar la función para imputar los valores de 'baths'
ventas = ventas.apply(imputar_promedio_baths, axis=1)
# Crear un histograma de baths
fig = px.histogram(ventas, x='baths', title='Histograma de cantidad de baños',
                   labels={'baths': 'Baños'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Baños', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()

Surface#

# Crear un histograma de 'surface'
fig = px.histogram(ventas, x='surface', title='Histograma de superficie',
                   labels={'surface': 'Superficie (m²)'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Superficie (m²)', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()
# Filtrar filas con valores de 'surface' superiores a 50
surface_mayor_30 = ventas[ventas['surface'] > 100000]

# Mostrar las filas resultantes
print(surface_mayor_30)
              lat       long     category         price     surface  rooms  \
24662    0.000000        NaN         Casa  8.500000e+08  24100000.0    5.0   
26382    0.000000        NaN         Casa  5.900000e+08    245000.0    7.0   
26263    0.000000        NaN         Casa  6.000000e+08    152215.0    4.0   
28891    0.000000        NaN         Casa  3.500000e+08   6000000.0    5.0   
29557    0.000000        NaN         Casa  3.000000e+08    238806.0    7.0   
27922    0.000000        NaN         Casa  4.200000e+08    200000.0    4.0   
30643    0.000000        NaN         Casa  2.400000e+08   5501150.0    7.0   
28278    0.000000        NaN         Casa  3.900000e+08   6485355.0    4.0   
16267    0.000000        NaN  Apartamento  2.250000e+08   5762233.0    3.0   
1978     0.000000        NaN  Apartamento  7.500000e+08    139581.0    3.0   
28263    0.000000        NaN         Casa  3.950000e+08  88888888.0    2.0   
396510   3.298540 -76.530930         Casa  4.000000e+08    170000.0    5.0   
376989   3.407871 -76.550545         Casa  5.200000e+08    300300.0    4.0   
371271   3.439657 -76.528442         Casa  3.850000e+08    300000.0    6.0   
365076   3.459028 -76.535065         Casa  9.500000e+08    220000.0    6.0   
358402   4.487996 -74.098907         Casa  1.500000e+08  99999999.0    9.0   
347550   4.590851 -74.083054         Casa  7.000000e+08    190000.0    5.0   
344875   4.597900 -74.106216         Casa  5.650000e+08    565000.0    6.0   
322897   4.638382 -74.088768         Casa  4.000000e+08    111111.0    5.0   
322403   4.638403 -74.088768         Casa  2.700000e+08    111111.0    8.0   
321518   4.639140 -74.148041  Apartamento  6.500000e+08    130000.0    7.0   
320436   4.640376 -74.057983  Apartamento  3.600000e+08  36000000.0    1.0   
297792   4.663249 -74.062195         Casa  9.000000e+08    265000.0    6.0   
266763   4.690610 -74.107170         Casa  4.500000e+08    122000.0    4.0   
261755   4.693690 -74.099228         Casa  5.500000e+08    195000.0    8.0   
238532   4.706289 -74.036522  Apartamento  8.600000e+08  71818181.0    3.0   
230309   4.713731 -74.097633         Casa  3.500000e+08    180000.0    6.0   
228920   4.714928 -74.104393         Casa  3.200000e+08    123456.0    7.0   
219876   4.721099 -74.073181         Casa  6.500000e+08    158000.0    4.0   
184758   4.755840 -74.052406         Casa  7.500000e+08    180800.0    4.0   
184440   4.756157 -74.045708  Apartamento  6.390000e+08    105000.0    3.0   
175052   4.804483 -75.687645  Apartamento  8.500000e+08  25605222.0    5.0   
167232   5.026003 -74.030014         Casa  3.300000e+08    111111.0    7.0   
166693   5.026027 -74.030014         Casa  1.000000e+08    865225.0    6.0   
166300   5.030080 -75.457657         Casa  2.300000e+08    111111.0    4.0   
133378   6.202770 -75.554443  Apartamento  1.600000e+09    270000.0    4.0   
130907   6.206545 -75.576408  Apartamento  4.400000e+08    418000.0    4.0   
112029   6.243726 -75.610153  Apartamento  1.500000e+09    347347.0    4.0   
91712   10.384208 -75.482521         Casa  2.700000e+08    330000.0    3.0   
89342   10.392638 -75.546875  Apartamento  1.820000e+09    182182.0    3.0   
69898   10.928613 -74.807098         Casa  1.000000e+08    123889.0    2.0   
63393   10.990490 -74.819481  Apartamento  4.500000e+08    332000.0    3.0   
45182   11.011632 -74.822914  Apartamento  1.240000e+09    561535.0    4.0   
42323   11.014119 -74.813507  Apartamento  4.250000e+08    154250.0    3.0   

        baths  park        ciudad                  barrio  Área privada  \
24662     3.0   2.0        Bogotá           Villa Claudia      155000.0   
26382     4.0   1.0        Bogotá                Castilla      137000.0   
26263     3.0   2.0        Bogotá            Cedrito Gold           NaN   
28891     5.0   0.0        Bogotá                Zona Sur           NaN   
29557     3.0   0.0        Bogotá              Tunjuelito           NaN   
27922     3.0   0.0      Medellín           Villa Hermosa      200000.0   
30643     3.0   0.0          Cali             Alirio mora           NaN   
28278     2.0   0.0        Bogotá      Portales del norte           NaN   
16267     2.0   0.0      Medellín          Itagui san Pio           NaN   
1978      4.0   0.0        Bogotá           Santa Bárbara           NaN   
28263     1.0   0.0        Bogotá  Ciudad Kennedy Central     8888888.0   
396510    5.0   3.0          Cali                Zona Sur         170.0   
376989    3.0   3.0          Cali         Cuarto de Legua         300.0   
371271    4.0   1.0          Cali                Aranjuez      300000.0   
365076    3.0   3.0          Cali                              220000.0   
358402    3.0   0.0        Bogotá                    Usme           NaN   
347550    3.0   2.0        Bogotá           Ciudad Montes           NaN   
344875    4.0   2.0        Bogotá            Santa Isabel           NaN   
322897    2.0   0.0  Barranquilla         Buena Esperanza      111111.0   
322403    3.0   0.0        Bogotá            Patio Bonito      111111.0   
321518    5.0   0.0        Bogotá                   Techo         130.0   
320436    2.0   2.0        Bogotá          Zona Chapinero          45.0   
297792    4.0   0.0        Bogotá              San Felipe           NaN   
266763    3.0   0.0        Bogotá              La clarita           NaN   
261755    4.0   0.0        Bogotá                  Tabora         195.0   
238532    3.0   2.0        Bogotá             La Carolina           NaN   
230309    4.0   0.0        Bogotá                  Bachue      180000.0   
228920    3.0   0.0        Bogotá                  Bachue      123456.0   
219876    4.0   2.0        Bogotá     Ciudad Jardin Norte      158000.0   
184758    3.0   2.0        Bogotá         Villa Del Prado      180800.0   
184440    3.0   2.0        Bogotá         Nueva Autopista      105000.0   
175052    5.0   4.0       Pereira                 PINARES           NaN   
167232    4.0   0.0        Bogotá                  Bachue      111111.0   
166693    2.0   0.0        Bogotá              La paz sur      865225.0   
166300    1.0   0.0     Manizales                 La Enea           NaN   
133378    5.0   3.0      Medellín              El Poblado         270.0   
130907    2.0   1.0      Medellín            PATIO BONITO         136.0   
112029    5.0   2.0      Medellín                LAURELES         347.0   
91712     2.0   0.0     Cartagena           El El Socorro      330000.0   
89342     3.0   2.0     Cartagena          CASTILLOGRANDE         182.0   
69898     1.0   0.0  Barranquilla            Soledad 2000      123889.0   
63393     4.0   1.0  Barranquilla           Ciudad Jardín           NaN   
45182     4.0   4.0  Barranquilla       ALTOS DEL LIMONAR           NaN   
42323     3.0   1.0  Barranquilla         Altos De Riomar           NaN   

        Área Const.     Admón  Estrato  Estado      Antiguedad  Piso No  \
24662    24100000.0       NaN      3.0     4.0  Más de 30 años      2.0   
26382      245000.0       NaN      3.0     5.0  Más de 30 años      2.0   
26263      152215.0       NaN      4.0     5.0    16 a 30 años      3.0   
28891     6000000.0   50000.0      2.0     3.0  Más de 30 años      3.0   
29557      238806.0       NaN      2.0     3.0    16 a 30 años      2.0   
27922      200000.0    5000.0      3.0     4.0            None      NaN   
30643     5501150.0       NaN      NaN     NaN            None      NaN   
28278     6485355.0       NaN      NaN     NaN            None      NaN   
16267     5762233.0       NaN      NaN     NaN            None      NaN   
1978       139581.0       NaN      NaN     NaN            None      NaN   
28263    88888888.0       NaN      3.0     5.0      1 a 8 años      2.0   
396510     170000.0  280000.0      4.0     5.0      1 a 8 años      2.0   
376989     300300.0       NaN      5.0     5.0    16 a 30 años      1.0   
371271     300000.0       NaN      3.0     3.0      1 a 8 años      2.0   
365076     220000.0       NaN      1.0     5.0            None      NaN   
358402   99999999.0       NaN      2.0     4.0      1 a 8 años      4.0   
347550     190000.0       NaN      3.0     NaN            None      NaN   
344875     565000.0       NaN      3.0     4.0     9 a 15 años      3.0   
322897     111111.0       NaN      3.0     NaN  Más de 30 años      NaN   
322403     111111.0       NaN      2.0     NaN            None      NaN   
321518     130000.0       NaN      2.0     3.0      1 a 8 años      6.0   
320436   36000000.0       NaN      5.0     5.0     9 a 15 años      2.0   
297792     265000.0       NaN      3.0     3.0            None      NaN   
266763     122000.0       NaN      NaN     NaN            None      NaN   
261755     195000.0       NaN      3.0     4.0    16 a 30 años      2.0   
238532   71818181.0  800000.0      6.0     5.0      1 a 8 años      8.0   
230309     180000.0       NaN      2.0     5.0    16 a 30 años      NaN   
228920     123456.0       NaN      2.0     NaN            None      NaN   
219876     158000.0  407000.0      4.0     NaN            None      NaN   
184758     180800.0       NaN      4.0     NaN            None      NaN   
184440     105000.0  550000.0      5.0     5.0      1 a 8 años      3.0   
175052   25605222.0       NaN      6.0     5.0     9 a 15 años      8.0   
167232     111111.0       NaN      2.0     NaN    16 a 30 años      NaN   
166693     865225.0       NaN      1.0     NaN            None      NaN   
166300     111111.0       NaN      3.0     NaN    16 a 30 años      NaN   
133378     270000.0  674000.0      6.0     5.0     9 a 15 años      1.0   
130907     418000.0       NaN      6.0     NaN    16 a 30 años      NaN   
112029     347347.0  900000.0      5.0     NaN     9 a 15 años      NaN   
91712      330000.0       NaN      3.0     NaN  Más de 30 años      NaN   
89342      182182.0       NaN      6.0     NaN     9 a 15 años      8.0   
69898      123889.0       NaN      2.0     NaN     9 a 15 años      NaN   
63393      332000.0       NaN      5.0     5.0      1 a 8 años      5.0   
45182      561535.0       NaN      6.0     NaN            None      NaN   
42323      154250.0       0.0      6.0     4.0    16 a 30 años      NaN   

       Tipo de Apartamento                  Sector  
24662            No Aplica                Zona Sur  
26382            No Aplica                Castilla  
26263            No Aplica                     NaN  
28891            No Aplica                Zona Sur  
29557            No Aplica              Tunjuelito  
27922            No Aplica           Villa Hermosa  
30643            No Aplica                     NaN  
28278            No Aplica                     NaN  
16267                 None                     NaN  
1978                  None           Santa Bárbara  
28263            No Aplica  Ciudad Kennedy Central  
396510           No Aplica                Zona Sur  
376989           No Aplica                Zona Sur  
371271           No Aplica                Aranjuez  
365076           No Aplica                     NaN  
358402           No Aplica                    Usme  
347550           No Aplica           Ciudad Montes  
344875           No Aplica            Santa Isabel  
322897           No Aplica                     NaN  
322403           No Aplica            Patio Bonito  
321518              Duplex                   Techo  
320436                None          Zona Chapinero  
297792           No Aplica              San Felipe  
266763           No Aplica                     NaN  
261755           No Aplica                  Tabora  
238532           PentHouse             La Carolina  
230309           No Aplica                  Bachue  
228920           No Aplica                  Bachue  
219876           No Aplica     Ciudad Jardin Norte  
184758           No Aplica         Villa Del Prado  
184440                None         Nueva Autopista  
175052           PentHouse    Circunvalar - Alamos  
167232           No Aplica                  Bachue  
166693           No Aplica                Zona Sur  
166300           No Aplica                     NaN  
133378                None              El Poblado  
130907                None                     NaN  
112029              Duplex                     NaN  
91712            No Aplica                     NaN  
89342                 None                     NaN  
69898            No Aplica                     NaN  
63393                 None  Norte Centro Histórico  
45182                 None                     NaN  
42323                 None                     NaN  
# Definir una función para imputar el promedio de 'surface'
def imputar_promedio_surface(row):
    if row['surface'] > 10000:
        # Filtrar filas con la misma ciudad, categoría y estrato
        filtro = ventas[(ventas['ciudad'] == row['ciudad']) &
                        (ventas['category'] == row['category']) &
                        (ventas['Estrato'] == row['Estrato'])]
        
        # Calcular el promedio de 'surface' para el grupo
        promedio_surface = filtro['surface'].mean()
        
        # Imputar el valor promedio
        row['surface'] = promedio_surface
    
    return row

# Aplicar la función para imputar los valores de 'surface'
ventas = ventas.apply(imputar_promedio_surface, axis=1)
# Crear un histograma de 'surface'
fig = px.histogram(ventas, x='surface', title='Histograma de superficie',
                   labels={'surface': 'Superficie (m²)'},
                   opacity=0.8)

# Personalizar el gráfico
fig.update_layout(xaxis_title='Superficie (m²)', yaxis_title='Frecuencia')
fig.update_traces(marker_line_width=1.5, marker_line_color='black')

# Mostrar el gráfico
fig.show()

Análisis de datos nulos o faltantes#

# DataFrame para el análisis de datos nulos
analisis_nulos = pd.DataFrame({
    'Conteo': ventas.isnull().sum(),
    'Porcentaje': (ventas.isnull().sum() / len(ventas)) * 100
})
print(analisis_nulos)
                     Conteo  Porcentaje
lat                     325    0.195278
long                  19548   11.745549
category                  0    0.000000
price                     0    0.000000
surface                   9    0.005408
rooms                  1510    0.907294
baths                  1248    0.749869
park                      0    0.000000
ciudad                    0    0.000000
barrio                    0    0.000000
Área privada          48636   29.223272
Área Const.               2    0.001202
Admón                 62404   37.495869
Estrato                2414    1.450468
Estado                69801   41.940407
Antiguedad            29276   17.590684
Piso No               66273   39.820584
Tipo de Apartamento  110967   66.675279
Sector                34869   20.951277
# Gráfico para el conteo de valores nulos
fig_conteo = px.bar(analisis_nulos, x=analisis_nulos.index, y='Conteo', title='Conteo de valores nulos por columna')
fig_conteo.update_layout(xaxis_title='Columnas', yaxis_title='Conteo de valores nulos', xaxis_tickangle=-45)
fig_conteo.show()

# Gráfico para el porcentaje de valores nulos
fig_porcentaje = px.bar(analisis_nulos, x=analisis_nulos.index, y='Porcentaje', title='Porcentaje de valores nulos por columna')
fig_porcentaje.update_layout(xaxis_title='Columnas', yaxis_title='Porcentaje de valores nulos (%)', xaxis_tickangle=-45)
fig_porcentaje.show()
# Mapa de calor de datos faltantes
plt.figure(figsize=(12, 6))
sns.heatmap(ventas.isnull(), cbar=False, cmap='Blues_r')
plt.title('Mapa de calor de datos faltantes')
plt.xticks(rotation=45, ha='right', fontsize=12)
plt.yticks(fontsize=12)
plt.show()
_images/a3261f958d35140550ec50507725798390cc48848eea56a8f8dadc2704f8be89.png
# Establecer estilo de gráfico
plt.style.use('ggplot')

# Crear el dendrograma de datos faltantes
msno.dendrogram(ventas, figsize=(12, 6))

# Personalizar el gráfico
plt.title('Dendrograma de datos faltantes', fontsize=16)
plt.ylabel('Número de datos faltantes', fontsize=14)
plt.xlabel('Columnas', fontsize=14)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)

# Mostrar el gráfico
plt.show()
_images/175913223d87bc4b5a867eaef7b986e21f23746f841608cca6871968fbb8ab5b.png

Imputación/eliminación de datos nulos#

Eliminación:

# Eliminar filas donde 'price' es nulo
ventas = ventas.dropna(subset=['price'])
# Eliminar filas donde 'rooms' es nulo
ventas = ventas.dropna(subset=['rooms'])
# Eliminar filas donde 'baths' es nulo
ventas = ventas.dropna(subset=['baths'])
# Eliminar filas donde 'Área Const.' es nulo
ventas = ventas.dropna(subset=['Área Const.'])

Imputar latitudes y longitudes de acuerdo con las latitudes y longitudes de las viviendas que estén ubicadas en la misma cuidad y sector.

# Calcular las coordenadas promedio por ciudad y sector
coordenadas_promedio = ventas.groupby(['ciudad', 'Sector']).agg({
    'lat': 'mean',
    'long': 'mean'
}).reset_index()

# Renombrar columnas para evitar conflictos durante el merge
coordenadas_promedio = coordenadas_promedio.rename(columns={'lat': 'lat_promedio', 'long': 'long_promedio'})

# Unir el DataFrame original con las coordenadas promedio
ventas = ventas.merge(coordenadas_promedio, on=['ciudad', 'Sector'], how='left')

# Imputar los valores faltantes con las coordenadas promedio
ventas['lat'] = ventas.apply(lambda row: row['lat_promedio'] if pd.isnull(row['lat']) else row['lat'], axis=1)
ventas['long'] = ventas.apply(lambda row: row['long_promedio'] if pd.isnull(row['long']) else row['long'], axis=1)

# Eliminar las columnas de coordenadas promedio
ventas = ventas.drop(columns=['lat_promedio', 'long_promedio'])
# Calcular las coordenadas promedio por ciudad y barrio
coordenadas_promedio = ventas.groupby(['ciudad', 'barrio']).agg({
    'lat': 'mean',
    'long': 'mean'
}).reset_index()

# Renombrar columnas para evitar conflictos durante el merge
coordenadas_promedio = coordenadas_promedio.rename(columns={'lat': 'lat_promedio', 'long': 'long_promedio'})

# Unir el DataFrame original con las coordenadas promedio
ventas = ventas.merge(coordenadas_promedio, on=['ciudad', 'barrio'], how='left')

# Imputar los valores faltantes con las coordenadas promedio
ventas['lat'] = ventas.apply(lambda row: row['lat_promedio'] if pd.isnull(row['lat']) else row['lat'], axis=1)
ventas['long'] = ventas.apply(lambda row: row['long_promedio'] if pd.isnull(row['long']) else row['long'], axis=1)

# Eliminar las columnas de coordenadas promedio
ventas = ventas.drop(columns=['lat_promedio', 'long_promedio'])
# Gráfico para el conteo de valores nulos
fig_conteo = px.bar(analisis_nulos, x=analisis_nulos.index, y='Conteo', title='Conteo de valores nulos por columna')
fig_conteo.update_layout(xaxis_title='Columnas', yaxis_title='Conteo de valores nulos', xaxis_tickangle=-45)
fig_conteo.show()

# Gráfico para el porcentaje de valores nulos
fig_porcentaje = px.bar(analisis_nulos, x=analisis_nulos.index, y='Porcentaje', title='Porcentaje de valores nulos por columna')
fig_porcentaje.update_layout(xaxis_title='Columnas', yaxis_title='Porcentaje de valores nulos (%)', xaxis_tickangle=-45)
fig_porcentaje.show()
print(analisis_nulos)
                     Conteo  Porcentaje
lat                     325    0.195278
long                  19548   11.745549
category                  0    0.000000
price                     0    0.000000
surface                   9    0.005408
rooms                  1510    0.907294
baths                  1248    0.749869
park                      0    0.000000
ciudad                    0    0.000000
barrio                    0    0.000000
Área privada          48636   29.223272
Área Const.               2    0.001202
Admón                 62404   37.495869
Estrato                2414    1.450468
Estado                69801   41.940407
Antiguedad            29276   17.590684
Piso No               66273   39.820584
Tipo de Apartamento  110967   66.675279
Sector                34869   20.951277

Análisis Exploratorio de Datos#

# Crear el reporte de análisis exploratorio de datos
reporte = sv.analyze(ventas, target_feat='price')

# Mostrar el reporte en el notebook
reporte.show_notebook()
  • Boxplot

fig = px.box(ventas, x='category', y='price')
fig.show()
fig = px.box(ventas, x='ciudad', y='price')
fig.show()
fig = px.box(ventas, x='Estrato', y='price')
fig.show()

Continuará…#